package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseAttrInfo;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    //  这个GoodsRepository 是我们自定义的接口.
    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Override
    public void upperGoods(Long skuId) {
        //  创建一个Goods 对象
        Goods goods = new Goods();
        //  根据skuId 获取skuInfo; 为什么要supplyAsync ?
        CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            SkuInfo skuInfo = this.productFeignClient.getSkuInfo(skuId);
            //  给goods 赋值
            goods.setId(skuId);
            //  商品名称:
            goods.setTitle(skuInfo.getSkuName());
            goods.setDefaultImg(skuInfo.getSkuDefaultImg());
            //  skuInfo: 查询缓存：就有那么一种可能会出现瞬间不一致;
            goods.setPrice(skuInfo.getPrice().doubleValue());
            //  实时价格：
            //  goods.setPrice(this.productFeignClient.getSkuPrice(skuId).doubleValue());
            goods.setCreateTime(new Date());
            return skuInfo;
        },threadPoolExecutor);

        CompletableFuture<Void> tmCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            //  获取品牌数据
            BaseTrademark trademark = this.productFeignClient.getTrademark(skuInfo.getTmId());
            goods.setTmId(trademark.getId());
            goods.setTmName(trademark.getTmName());
            goods.setTmLogoUrl(trademark.getLogoUrl());
        },threadPoolExecutor);

        CompletableFuture<Void> categoryCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            //  获取分类数据
            BaseCategoryView categoryView = this.productFeignClient.getCategoryView(skuInfo.getCategory3Id());

            goods.setCategory1Id(categoryView.getCategory1Id());
            goods.setCategory2Id(categoryView.getCategory2Id());
            goods.setCategory3Id(categoryView.getCategory3Id());
            goods.setCategory1Name(categoryView.getCategory1Name());
            goods.setCategory2Name(categoryView.getCategory2Name());
            goods.setCategory3Name(categoryView.getCategory3Name());
        },threadPoolExecutor);


        CompletableFuture<Void> attrCompletableFuture = CompletableFuture.runAsync(() -> {
            //  获取平台属性：
            List<BaseAttrInfo> attrList = this.productFeignClient.getAttrList(skuId);
            List<SearchAttr> attrLists = attrList.stream().map(baseAttrInfo -> {
                SearchAttr searchAttr = new SearchAttr();
                searchAttr.setAttrId(baseAttrInfo.getId());
                searchAttr.setAttrName(baseAttrInfo.getAttrName());
                //  不懂呀！ 给平台属性值名称赋值。
                searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
                return searchAttr;
            }).collect(Collectors.toList());

            //  集合存储的平台属性 SearchAttr
            goods.setAttrs(attrLists);
        },threadPoolExecutor);

        //  多任务组合：
        CompletableFuture.allOf(
                skuInfoCompletableFuture,
                categoryCompletableFuture,
                tmCompletableFuture,
                attrCompletableFuture
        ).join();
        //  保存数据
        goodsRepository.save(goods);
    }

    @Override
    public void lowerGoods(Long skuId) {
        //  下架：
        this.goodsRepository.deleteById(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        //  借助redis来存储用户访问的次数！
        //  数据类型： zSet zincrby hotScore 1 11 ; String incr key; key=skuId
        String key = "hotScore";
        Double count = this.redisTemplate.opsForZSet().incrementScore(key, skuId, 1);
        //  判断
        if (count%10==0){
            //  更新es 中的hotScore;
            Optional<Goods> goodsOptional = this.goodsRepository.findById(skuId);
            Goods goods = goodsOptional.get();
            goods.setHotScore(count.longValue());
            this.goodsRepository.save(goods);
        }
    }

    @Override
    public SearchResponseVo search(SearchParam searchParam) throws IOException {
        /*
        1.  先准备 dsl 语句！
        2.  执行dsl 语句！
        3.  获取返回值结果封装到 SearchResponseVo
         */

        //  创建查询请求对象
        SearchRequest searchRequest = this.queryDsl(searchParam);
        //  执行dsl 语句！ searchResponse {返回的所有数据}
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //  将数据封装到 SearchResponseVo
        SearchResponseVo searchResponseVo = this.parseResultSearchResponse(searchResponse);
        /*
        本质就是给这个对象 SearchResponseVo 的每个属性赋值！
        private List<SearchResponseTmVo> trademarkList;
        private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        private List<Goods> goodsList = new ArrayList<>();
        private Long total;//总记录数
        这四个属性在上一个方法赋值！parseResultSearchResponse();
   -------------------------------------------------------------

        private Integer pageSize;//每页显示的内容
        private Integer pageNo;//当前页面
        private Long totalPages;
         */
        //  默认显示条数： 3
        searchResponseVo.setPageSize(searchParam.getPageSize());
        //  默认当前页： 1
        searchResponseVo.setPageNo(searchParam.getPageNo());
        //  总页数：totalPages
        //  10 3 4 | 9 3 3
        //  Long totalPages = (searchResponseVo.getTotal()%searchParam.getPageSize()==0?searchResponseVo.getTotal()/searchParam.getPageSize():searchResponseVo.getTotal()/searchParam.getPageSize()+1)
        Long totalPages = (searchResponseVo.getTotal() + searchParam.getPageSize() - 1)/searchParam.getPageSize();
        searchResponseVo.setTotalPages(totalPages);
        return searchResponseVo;
    }


    /**
     * 封装返回数据
     * @param searchResponse es 中的所有数据！ Json
     * @return
     */
    private SearchResponseVo parseResultSearchResponse(SearchResponse searchResponse) {
        //  声明对象：
        SearchResponseVo searchResponseVo = new SearchResponseVo();
         /*
            本质就是给这个对象 SearchResponseVo 的每个属性赋值！
            private List<SearchResponseTmVo> trademarkList;
            private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
            private List<Goods> goodsList = new ArrayList<>();
            private Long total;//总记录数
          */

        // 总记录数：
        SearchHits hits = searchResponse.getHits();
        searchResponseVo.setTotal(hits.getTotalHits().value);

        //  获取元数据信息：
        SearchHit[] subHits = hits.getHits();
        //  赋值商品集合。
        List<Goods> goodsList = new ArrayList<>();
        for (SearchHit subHit : subHits) {
            //  获取_source 节点取数据
            String sourceAsString = subHit.getSourceAsString();
            //  进行数据类型转换：
            Goods goods = JSON.parseObject(sourceAsString, Goods.class);
            //  判断是否有高亮显示：
            if (subHit.getHighlightFields().get("title")!=null){
                //  有高亮显示
                String title = subHit.getHighlightFields().get("title").getFragments()[0].toString();
                goods.setTitle(title);
            }
            //  添加goods 到集合中.
            goodsList.add(goods);
        }
        searchResponseVo.setGoodsList(goodsList);
        //  获取品牌数据： 从聚合获取！
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        //  获取品牌聚合数据 ; 写dsl 语句时，给的名字！
        //  根据tmId 数据类型进行转换
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        //  获取集合中的某一个数据，然后再放入另一个集合！
        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map(bucket -> {
            //  创建一个对象
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
            //  获取bucket 的key
            String tmId = bucket.getKeyAsString();
            searchResponseTmVo.setTmId(Long.parseLong(tmId));
            //  获取子聚合 获取品牌名
            ParsedStringTerms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmName(tmName);
            //  获取品牌的LOGO
            ParsedStringTerms tmLogoUrlAgg = bucket.getAggregations().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmLogoUrl(tmLogoUrl);
            //  返回数据.
            return searchResponseTmVo;
        }).collect(Collectors.toList());
        searchResponseVo.setTrademarkList(trademarkList);

        //  获取平台属性数据： 数据类型nested;
        ParsedNested attrsAgg = (ParsedNested) aggregationMap.get("attrsAgg");
        //  获取平台属性Id 聚合数据
        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> attrsList = attrIdAgg.getBuckets().stream().map(bucket -> {
            //  创建对象
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
            //  平台属性Id
            String attrId = bucket.getKeyAsString();
            searchResponseAttrVo.setAttrId(Long.parseLong(attrId));
            //  获取平台属性名：
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseAttrVo.setAttrName(attrName);
            //  获取平台属性值集合：
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
            //  map 映射
            //            List<String> stringList = attrValueAgg.getBuckets().stream().map(bucket1 -> {
            //                String attrValue = bucket1.getKeyAsString();
            //                return attrValue;
            //            }).collect(Collectors.toList());
            //  方法引用：  return bucket1.getKeyAsString();
            List<String> stringList = attrValueAgg.getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());

            //  设置属性值名集合
            searchResponseAttrVo.setAttrValueList(stringList);

            return searchResponseAttrVo;
        }).collect(Collectors.toList());
        //  赋值平台属性集合。
        searchResponseVo.setAttrsList(attrsList);
        //  返回查询之后的结果
        return searchResponseVo;
    }

    private SearchRequest queryDsl(SearchParam searchParam) {
        //  创建一个查询器：
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //  创建一个bool 对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //  判断：用户是否通过分类Id 进行检索，或根据关键词进行检索！
        if (!StringUtils.isEmpty(searchParam.getCategory1Id())){
            //  QueryBuilders 构建所有条件的工具类.
            //  {bool --- filter --- term [key-value]}
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",searchParam.getCategory1Id()));
        }
        //  二级分类Id
        if (!StringUtils.isEmpty(searchParam.getCategory2Id())){
            //  QueryBuilders 构建所有条件的工具类.
            //  {bool --- filter --- term [key-value]}
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",searchParam.getCategory2Id()));
        }
        //  三级分类Id
        if (!StringUtils.isEmpty(searchParam.getCategory3Id())){
            //  QueryBuilders 构建所有条件的工具类.
            //  {bool --- filter --- term [key-value]}
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",searchParam.getCategory3Id()));
        }
        //  判断用户是否根据关键词检索
        if (!StringUtils.isEmpty(searchParam.getKeyword())){
            //  有数据；
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",searchParam.getKeyword()).operator(Operator.AND));
            //  无检索不高亮：
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("title");
            highlightBuilder.preTags("<span style=color:red>");
            highlightBuilder.postTags("</span>");
            //  将高亮设置给查询器！
            searchSourceBuilder.highlighter(highlightBuilder);
        }

        //  品牌Id 过滤. 3:华为
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)){
            //  根据：号分割
            String[] split = trademark.split(":");
            if (split!=null && split.length==2){
                //  获取数组的第一个值！ split[0];
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
            }
        }

        //  平台属性过滤: 用户可以通过多个平台属性值进行过滤！props=24:128G:机身内存&props=23:8G:运行内存
        String[] props = searchParam.getProps();
        //  判断
        if (props!=null && props.length>0){
            for (String prop : props) {
                //  prop = 24:128G:机身内存  prop = 23:8G:运行内存
                String[] split = prop.split(":");
                if (split!=null && split.length==3){
                    //  创建内部的bool
                    BoolQueryBuilder innerBoolQueryBuilder = QueryBuilders.boolQuery();
                    innerBoolQueryBuilder.filter(QueryBuilders.termQuery("attrs.attrId",split[0]));
                    innerBoolQueryBuilder.filter(QueryBuilders.termQuery("attrs.attrValue",split[1]));
                    //  外层：
                    boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs",innerBoolQueryBuilder, ScoreMode.None));
                }
            }
        }
        //  判断：
//        if (props!=null && props.length>0){
//            for (String prop : props) {
//                String[] split = prop.split(":");
//                if (split!=null && split.length==3){
//                    //  创建中间层：
//                    BoolQueryBuilder midBoolQueryBuilder = QueryBuilders.boolQuery();
//                    //  创建内部的bool
//                    BoolQueryBuilder innerBoolQueryBuilder = QueryBuilders.boolQuery();
//                    innerBoolQueryBuilder.filter(QueryBuilders.termQuery("attrs.attrId",split[0]));
//                    innerBoolQueryBuilder.filter(QueryBuilders.termQuery("attrs.attrValue",split[1]));
//                    //  内层放入中间层。
//                    midBoolQueryBuilder.must(QueryBuilders.nestedQuery("attrs",innerBoolQueryBuilder,ScoreMode.None));
//                    //  将中间层放入外层
//                    boolQueryBuilder.filter(midBoolQueryBuilder);
//                }
//            }
//        }

        //  分页： 3 2   1: 0,2  2: 2,2
        //  设置初始条数
        int from = (searchParam.getPageNo()-1)*searchParam.getPageSize();
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(searchParam.getPageSize());

        //  排序：
        //  获取用户按照哪个字段进行排序同时排序规则！ order=1:desc  order=1:asc order=2:desc  order=2:asc
        //  判断：
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)){
            //  分割：
            String[] split = order.split(":");
            if (split!=null && split.length == 2 ){
                //  声明一个字段
                String field = "";
                switch (split[0]) {
                    case "1":
                        field = "hotScore";
                        break;
                    case "2":
                        field = "price";
                        break;
                }
                //  split[1] asc 或 desc
                searchSourceBuilder.sort(field, "asc".equals(split[1])?SortOrder.ASC:SortOrder.DESC);
            }
        }else {
            //  默认按照热度降序
            searchSourceBuilder.sort("hotScore", SortOrder.DESC);
        }

        //  聚合：
        //  品牌聚合：
        searchSourceBuilder.aggregation(
                AggregationBuilders.terms("tmIdAgg").field("tmId")
                        .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                        .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl"))
        );
        //  平台属性聚合：
        searchSourceBuilder.aggregation(
                AggregationBuilders.nested("attrsAgg","attrs")
                        .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                                .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                                .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))
                        )
        );

        // { query -- bool }
        searchSourceBuilder.query(boolQueryBuilder);

        //  GET /goods/_search{ } goods 索引库
        //  声明一个对象
        SearchRequest searchRequest = new SearchRequest("goods");
        searchRequest.source(searchSourceBuilder);
        //  设置哪些数据需要查询，哪些不需要查询！ 品牌；平台属性数据，显示的时候从聚合中获取就可以了！ 分类Id 只做查询品牌，不需要显示！
        searchSourceBuilder.fetchSource(new String[] {"id","defaultImg","title", "price","createTime"},null);
        System.out.println("dsl:\t"+searchSourceBuilder.toString());
        //  返回对象
        return searchRequest;
    }
}
